/*   Copyright 2012 Julian Aloofi

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

#include "Board.hpp"

Tacticus::Board::Board()
{
    currentPlayer = NOBODY;
    gameOver = false;

    gameBoard = std::vector<std::vector<Field> >(10, std::vector<Field>
        (10, Field(EMPTY)));
    gameBoard[2][4] = Field(IMPASSABLE);
    gameBoard[3][4] = Field(IMPASSABLE);
    gameBoard[2][5] = Field(IMPASSABLE);
    gameBoard[3][5] = Field(IMPASSABLE);
    gameBoard[6][4] = Field(IMPASSABLE);
    gameBoard[7][4] = Field(IMPASSABLE);
    gameBoard[6][5] = Field(IMPASSABLE);
    gameBoard[7][5] = Field(IMPASSABLE);

    toBePlaced = std::map<Field, int>();
    toBePlaced[Field(RED, MARSHAL)]     = 1;
    toBePlaced[Field(RED, GENERAL)]     = 1;
    toBePlaced[Field(RED, COLONEL)]     = 2;
    toBePlaced[Field(RED, MAJOR)]       = 3;
    toBePlaced[Field(RED, CAPTAIN)]     = 4;
    toBePlaced[Field(RED, LIEUTENANT)]  = 4;
    toBePlaced[Field(RED, SERGEANT)]    = 4;
    toBePlaced[Field(RED, MINER)]       = 5;
    toBePlaced[Field(RED, SCOUT)]       = 8;
    toBePlaced[Field(RED, SPY)]         = 1;
    toBePlaced[Field(RED, BOMB)]        = 6;
    toBePlaced[Field(RED, FLAG)]        = 1;
    toBePlaced[Field(BLUE, MARSHAL)]    = 1;
    toBePlaced[Field(BLUE, GENERAL)]    = 1;
    toBePlaced[Field(BLUE, COLONEL)]    = 2;
    toBePlaced[Field(BLUE, MAJOR)]      = 3;
    toBePlaced[Field(BLUE, CAPTAIN)]    = 4;
    toBePlaced[Field(BLUE, LIEUTENANT)] = 4;
    toBePlaced[Field(BLUE, SERGEANT)]   = 4;
    toBePlaced[Field(BLUE, MINER)]      = 5;
    toBePlaced[Field(BLUE, SCOUT)]      = 8;
    toBePlaced[Field(BLUE, SPY)]        = 1;
    toBePlaced[Field(BLUE, BOMB)]       = 6;
    toBePlaced[Field(BLUE, FLAG)]       = 1;
}

void Tacticus::Board::place(const Tacticus::Field &field,
    const Tacticus::Common::Coordinate &coordinate)
{
    if (currentPlayer != NOBODY)
    {
        throw Common::InvalidPlacementException("Placement already done");
    }
    if (!isValidPlacement(field, coordinate))
    {
        throw Common::InvalidPlacementException("Illegal placement");
    }

    if (gameBoard[coordinate.x][coordinate.y] != Field(EMPTY))
    {
        toBePlaced[gameBoard[coordinate.x][coordinate.y]]++;
    }
    gameBoard[coordinate.x][coordinate.y] = field;
    if (field.getPiece() != EMPTY)
    {
        toBePlaced[field]--;
    }
}

const std::pair<Tacticus::Common::CoordinateField,
    Tacticus::Common::CoordinateField> Tacticus::Board::makeTurn(
    const Common::Coordinate &origin, const Common::Coordinate &target)
{
    if (gameOver)
    {
        throw Common::InvalidTurnException("Game already over");
    }
    if (currentPlayer == NOBODY)
    {
        throw Common::InvalidTurnException("Placement not done yet");
    }
    if (!isValidTurn(origin, target) ||
        (gameBoard[origin.x][origin.y].getPlayer() != currentPlayer))
    {
        throw Common::InvalidTurnException("Illegal turn");
    }

    Field originField = gameBoard[origin.x][origin.y];
    Field targetField = gameBoard[target.x][target.y];

    std::pair<bool, bool> battleResults = originField.attack(targetField);

    // attacking piece leaves its field
    gameBoard[origin.x][origin.y] = Field();

    // if the attacked piece lost, we remove it
    if (battleResults.second == false)
        gameBoard[target.x][target.y] = Field();
    // if the attacking piece survived, it takes the attacked piece's place
    if (battleResults.first == true)
        gameBoard[target.x][target.y] = originField;

    if (targetField.getPiece() == FLAG)
    {
        gameOver = true;
    }
    if (targetField.getPlayer() != NOBODY)
    {
        if (!validTurnsAvailableFor(targetField.getPlayer()))
        {
            gameOver = true;
        }
    }

    if (!gameOver)
    {
        if (!validTurnsAvailableFor(originField.getPlayer()))
        {
            gameOver = true;
        }
        if (currentPlayer == RED)
            currentPlayer = BLUE;
        else
            currentPlayer = RED;
    }
    return std::pair<Common::CoordinateField, Common::CoordinateField>(
        Common::CoordinateField(origin, originField), Common::CoordinateField(
        target, targetField));
}

Tacticus::Field const &Tacticus::Board::getFieldAt(
    const Tacticus::Common::Coordinate &coordinate) const
{
    if (!isOnTheBoard(coordinate))
    {
        throw Common::AssumptionException("Tried to access invalid coordinate");
    }
    return gameBoard[coordinate.x][coordinate.y];
}

bool Tacticus::Board::validTurnsAvailableFor(const Player &player) const {
    using namespace Tacticus::Common;
    // loop through the board
    for (int x = 0; x < 10; x++)
    {
        for (int y = 0; y < 10; y++)
        {
            // continue if the piece we're looking at doesn't belong to player
            if (gameBoard[x][y].getPlayer() != player)
                continue;
            // check if a move to any adjacent field is possible
            if (isValidTurn(Coordinate(x, y), Coordinate(x+1.f, y))
                || isValidTurn(Coordinate(x, y), Coordinate(x-1.f, y))
                || isValidTurn(Coordinate(x, y), Coordinate(x, y+1.f))
                || isValidTurn(Coordinate(x, y), Coordinate(x, y-1.f)))
            {
                return true;
            }
        }
    }
    return false;
}

int Tacticus::Board::howManyToBePlaced(const Field &field) const
{
    return toBePlaced.find(field)->second;
}

Tacticus::Player Tacticus::Board::getCurrentPlayer() const
{
    return currentPlayer;
}

void Tacticus::Board::startGame()
{
    if (currentPlayer != NOBODY)
    {
        throw Common::AssumptionException(
            "Tried to start game that was already running");
    }
    currentPlayer = RED;
}

bool Tacticus::Board::isGameOver() const
{
    return gameOver;
}

bool Tacticus::Board::areAllPlaced(Tacticus::Player whichPlayer) const
{
    for (std::map<Field, int>::const_iterator it = toBePlaced.begin();
        it != toBePlaced.end(); it++)
    {
        if (it->first.getPlayer() != whichPlayer && whichPlayer != NOBODY)
            continue;
        if (it->second > 0)
            return false;
    }
    return true;
}

bool Tacticus::Board::isValidPlacement(const Tacticus::Field &field,
    const Common::Coordinate &coordinate) const
{
    if (!isOnTheBoard(coordinate))
        return false;
    if (field.getPiece() != EMPTY && toBePlaced.find(field)->second <= 0)
        return false;
    if (field.getPlayer() == RED && coordinate.y < 6.f)
        return false;
    if (field.getPlayer() == BLUE && coordinate.y > 3.f)
        return false;
    if (gameBoard[coordinate.x][coordinate.y].getPiece() == IMPASSABLE)
        return false;
    return true;
}

bool Tacticus::Board::isValidTurn(const Tacticus::Common::Coordinate &origin,
    const Tacticus::Common::Coordinate &target) const
{
    if (!areOnTheBoard(origin, target))
        return false;

    const Field &orig = gameBoard[origin.x][origin.y];
    const Field &targ = gameBoard[target.x][target.y];

    if (!orig.isValidAttack(targ))
        return false;

    if (orig.getPiece() != SCOUT &&
        (target.x > origin.x + 1 || target.x < origin.x - 1||
        target.y > origin.y + 1 || target.y < origin.y - 1))
        return false;
    if (origin.x != target.x && origin.y != target.y)
        return false;
    // if a scout is moving, check that every piece he walks over is empty
    if (orig.getPiece() == SCOUT)
    {
        int x = origin.x;
        int y = origin.y;
        do
        {
            if (target.x > origin.x + 1)
            {
                x++;
                if (x >= target.x)
                    break;
            }
            else if (target.x < origin.x - 1)
            {
                x--;
                if (x <= target.x)
                    break;
            }
            else if (target.y > origin.y + 1)
            {
                y++;
                if (y >= target.y)
                    break;
            }
            else if (target.y < origin.y - 1)
            {
                y--;
                if (y <= target.y)
                    break;
            }
            else
                break;

            if (gameBoard[x][y].getPiece() != EMPTY)
                return false;
        } while (true);
    }

    return true;
}
